This post demonstrates how oct2py can be used to run legacy Matlab/Octave code to load data saved in NeuroExplorer files into Python. As will be illustrated in a forthcoming post, this same approach can be used to run existing Matlab/Octave code in an integrated Jupyter notebook with Python kernel.

Matlab/Octave code for working with NEx files and a test file can be obtained from the NeuroExplorer website.

My lab has used an older set of m files written by Alex Kirillov (author of NeuroExplorer) and updated by us to deal with changes in Matlab over the years. We no longer use Matlab and our older m files for reading NEx files work perfectly well in GNU Octave. The three main files that we use are available from our GitHub repository: nex_info, nex_ts, and nex_cont.


In [1]:
import numpy as np
from scipy.io import loadmat
%load_ext oct2py.ipython

In [2]:
%cd ~/Desktop/Spikes-and-Fields/NEx-demo


/home/mark/Desktop/Spikes-and-Fields/NEx-demo

Read files in an Octave session

The command %octave is the "octave magic" function that let's you run raw Matlab/Octave code in a Python notebook.

For a batch of commenda, use %%octave in the first line of a code cell. The rest of the cell is pure Octave/Matlab code. %%octave -o var returns a variable from the cell directly to the Python memoryspace.


In [3]:
%octave [nvar, names, types] = nex_info('TestDataFileForNeuroshare.nex');


file = TestDataFileForNeuroshare.nex

%octave_pull and %octave_push are used to send variables between Octave and Python.


In [4]:
%octave_pull nvar names types
  • nvar is the number of variables in the NEx file
  • names are strings for each variable
  • types are the types of data (0-neuron, 1-event, 2-interval, 3-waveform, 4-population vector, 5-continuous variable, 6 - marker)

In [5]:
%whos


Variable   Type        Data/Info
--------------------------------
loadmat    function    <function loadmat at 0x7fd855b7c0d0>
names      ndarray     18: 18 elems, type `<U64`, 4608 bytes
np         module      <module 'numpy' from '/ho<...>kages/numpy/__init__.py'>
nvar       float64     18.0
types      ndarray     1x18: 18 elems, type `float64`, 144 bytes

Some issues come up with variable types using this approach, and a few adjustments are needed once the variables are pulled into Python.


In [6]:
nvar = nvar.astype(int)  # nvar should really be an integer
types = types.flatten()  # types should really be a flat array

In [7]:
%whos


Variable   Type        Data/Info
--------------------------------
loadmat    function    <function loadmat at 0x7fd855b7c0d0>
names      ndarray     18: 18 elems, type `<U64`, 4608 bytes
np         module      <module 'numpy' from '/ho<...>kages/numpy/__init__.py'>
nvar       int64       18
types      ndarray     18: 18 elems, type `float64`, 144 bytes

Let's load the neuron's time stamps into Python


In [8]:
names[types==0]


Out[8]:
array(['Neuron04a', 'Neuron05b', 'Neuron05c', 'Neuron06b', 'Neuron06d',
       'Neuron07a'], 
      dtype='<U64')

It is very easy to load the variables that are needed into the Octave session, save them into a mat file, and load them into Python using SciPy's loadmat function.

Let's say we want to bring in one of the continuous recordings (ContChannel01) and a behavioral event (Event04) for an LFP using the MNE toolbox.


In [9]:
%%octave
[~, Event04] = nex_ts('TestDataFileForNeuroshare.nex', 'Event04');
[adfreq, n, ts, fn, AD01] = nex_cont('TestDataFileForNeuroshare.nex', 'ContChannel01');




In [10]:
%octave_pull Event04 adfreq ts AD01
%whos


Variable   Type        Data/Info
--------------------------------
AD01       ndarray     1x610207: 610207 elems, type `float64`, 4881656 bytes (4.655509948730469 Mb)
Event04    ndarray     1x862: 862 elems, type `float64`, 6896 bytes
adfreq     float64     10000.0
loadmat    function    <function loadmat at 0x7fd855b7c0d0>
names      ndarray     18: 18 elems, type `<U64`, 4608 bytes
np         module      <module 'numpy' from '/ho<...>kages/numpy/__init__.py'>
nvar       int64       18
ts         float64     0.0004
types      ndarray     18: 18 elems, type `float64`, 144 bytes

Again, the time stamps and continuous variables need to be flattened.

The floating point numbers are fine. (adfreq is the sampling frequency and ts is the time difference between saving time stamps in the Plexon recording system and the continuous signals on the AD card.)


In [11]:
AD01 = AD01.flatten()
Event04 = Event04.flatten()
%whos


Variable   Type        Data/Info
--------------------------------
AD01       ndarray     610207: 610207 elems, type `float64`, 4881656 bytes (4.655509948730469 Mb)
Event04    ndarray     862: 862 elems, type `float64`, 6896 bytes
adfreq     float64     10000.0
loadmat    function    <function loadmat at 0x7fd855b7c0d0>
names      ndarray     18: 18 elems, type `<U64`, 4608 bytes
np         module      <module 'numpy' from '/ho<...>kages/numpy/__init__.py'>
nvar       int64       18
ts         float64     0.0004
types      ndarray     18: 18 elems, type `float64`, 144 bytes